// By Xeno
// Commented/edited by Tyrghen

// This is an invisible object which will hold in it's object variables (setVariable, getVariable) the events and objects which should
// be shared amongst all clients and the server. The reason behind this is that setVariable and getVariable will reliably send the value
// to all client computers.
// Note that it is LOCAL to the system. This is to allow for managing JIP players more easily by always adding all events without the need to check if they exist.
// Since the object is local it has been wiped out with the last connection.
KEventHolder = "HeliHEmpty" createVehicleLocal [0, 0, 0];

//  JIPH
// -------------------------
// This is an invisible object which will hold in it's object variables (setVariable, getVariable) variables which should be synced between
// all players, even if they join in progress (JIP).
// The scenario is this:
// Your server starts, in the Init.sqf you have a global variable (though local to each system) which got set to True. Ex: g_bArtilleryEnabled = True;
// Then, during the game, an event happens which will change the content of this variable, in our example, artillery is not available anymore.
// In a normal case, you'll change the value by firing an event which will be replicated on all connected systems. Ex: EventHandler : "ArtilStatusChanged" {g_bArtilleryEnabled = _This;}
// Now, a bit later, a player JIP, the Init.sqf got fired on his system, and for his computer, the global variable g_bArtilleryEnabled is set to True. Since the system
// has NO WAY to know the "ArtilStatusChanged" event has been fired. So for this player, Artillery is still available.
// The easiest way around it is to use variables attached to a vehicule which is present on all systems. Preferably, this object has been placed in the mission editor.
// Now all you have to do is call in your scripts the getVariable of the object and you're done :) It's synched between ALL users.
// WARNING!!! The check below is just in case we didn't add it in the editor, but is dangerous. You could end up with more than one object and lose synch.

// The following statement will attach an event to the change of the public variable.
// So when you change the content of this variable (doing  publicVariable "VarName"), it will fire the code below.
"x_n_e_gl" addPublicVariableEventHandler {
	// We use the value of the variable as argument to the XNetRunEvent function.
	// _this contains ["VarName", VarValue]
	(_this select 1) call XNetRunEvent;
};

// This function will add a new event code to the variables of the oEventHolder.
// So this code will be available on all targeted computers (the reason behind the switch below.
//
// Param 1: Target System
// Param 2: Event Name
// Param 3: Event Code
XNetAddEvent = {
	private ["_bAllowed", "_sEventName", "_cEventCode", "_aEventHandlers"];
	// We can call the function either with numbers of text, I find text more clear
	// The goal of the code below is to check if the code is run on the intended target system and then either set the variable to True or False.
	// ex: We want to set the event "GlobalChat" which only make sense on Player computers, so we'll do  ["CLIENT", "GlobalChar", {code}] call XNetAddEvent;
	//     The result is that it will be added to the KEventHolder ONLY if the code is executing on a client. On a server, _bAllowed will be false.
	If (typeName(_this select 0) == "STRING") then {
		_bAllowed = switch (toupper(_this select 0)) do {
			case "ALL": {true}; // all
			case "SERVER": {if (isServer) then {true} else {false}}; // server only
			case "CLIENT": {if (X_Client) then {true} else {false}}; // client only
			case "DEDICATED": {if (isDedicated) then {true} else {false}}; // dedicated only
			case "CLIENT2": {if (!isServer) then {true} else {false}}; // clients which are not the server
		};	
	} else {
		_bAllowed = switch (_this select 0) do {
			case 0: {true}; // all
			case 1: {if (isServer) then {true} else {false}}; // server only
			case 2: {if (X_Client) then {true} else {false}}; // client only
			case 3: {if (isDedicated) then {true} else {false}}; // dedicated only
			case 4: {if (!isServer) then {true} else {false}}; // clients which are not the server
		};	
	};
	
	if (_bAllowed) then {
		// Instancied variables for readability.
		_sEventName = _this select 1;
		_cEventCode = _this select 2;
		// 1. We retrieve the list of existing event handlers.
		_aEventHandlers = KEventHolder getVariable (_sEventName);
		// 2. If no event handlers have been defined so far, we make a new list of event handlers
		//    A list of handlers is better than a single one, in order to be able to add specific handlers
		//    for new modules without the need to touch the old code.
		//    Ex: You deploy your MHQ, you can have 3 handlers: 1) Sets the marker name to deployed. 2) Sends a radio message to everyone. 3) Lock the vehicle.
		if (isNil "_aEventHandlers") then {_aEventHandlers = []};
		// 3. We add the event handler to the list. The set [count.. is a nice trick to add to an array.
		_aEventHandlers set [count _aEventHandlers, _cEventCode];
		// 4. We overwrite the event handlers list present on the object with the updated one.
		KEventHolder setVariable [_sEventName, _aEventHandlers];
		// diag_log Format["(%1)[%2]: %3: %4 (%5)",diag_tickTime,"init.sqf","AddedHandler", _sEventName, count _aEventHandlers];
	};
};

// The opposite of the function above. It will remove not one event, but ALL of them.
//
// Param 1: Event Handler Name
XNetRemoveEvent = {
	private ["_sEventName"];
	// We receive an event as parameter and nil out the array of event handlers.
	_sEventName = _this;
	if (!isNil {KEventHolder getVariable _sEventName}) then {KEventHolder setVariable [_sEventName, nil]};
};

// This function will run the event handlers.
//
// Param 1: Event Handler Name
// Param 2: Argument list
XNetRunEvent = {
	private ["_sEventName","_aEventHandlers", "_aEventArguments"];
	_sEventName = _this select 0;
	// 1. We extract the list of Event Handlers
	_aEventHandlers = KEventHolder getVariable (_sEventName);
	// 2. We test if we actually got a list back or not.
	if (d_global_debug > 0) then { diag_log Format["(%1) Called Event %2: %3", diag_ticktime,_sEventName, _this select 1]; };
	if (!isNil "_aEventHandlers") then {
		diag_log Format["(%1)[%2]: %3: %4 (%5)",diag_tickTime,"netfunctions.sqf","XNetRunEvent", _this, count _aEventHandlers];
		// We extract the Arguments list (if any)
		_aEventArguments = _this select 1;
		if (!isNil "_aEventArguments") then {
			// We call EACH Event Handler with the same arguments.
			// As you see it makes extending capabilities an easy thing
			{_aEventArguments call _x} forEach _aEventHandlers;
		} else {
			// Same as above but without arguments.
			{call _x} forEach _aEventHandlers;
		};
	};
};

// This is what you'll really use to spread an event to all target systems.
// On the computer which generates the event, you'll do: "EventName" call XNetCallEvent
// This will trigger the event on all computer using the addPublicVariableEventHandler.
//
// Param 1: Array in the form ["EventName", [EventArgs]]
XNetCallEvent = {
	// We set the value of the Variable to the Arguments we'll use to call each event handler in XNetRunEvent.
	x_n_e_gl = _this;
	// The following statement will commit the variable and Raise a PublicVariableEvent on all systems EXCEPT the one which changed the variable.
	// This will have as effect to call XNetRunEvent on all systems beside the origin system.
	publicVariable "x_n_e_gl";
	// For this reason, we need to call the XNetRunEvent ourself.
	// diag_log Format["(%1)[%2]: %3: %4",diag_tickTime,"init.sqf","CallEvent", _this];
	_this call XNetRunEvent;
};

// There is an Hidden Helipad placed on the map in the mission itself which is global to all systems.
// The following call make the added variable "public" (synched between all systems) on the object.
// This allows later for calling it from any system and be sure that it's synched.
//
// Param 1: Variable Name
// Param 2: Variable Value
XNetSetJIP = {
	private["_sVariableName","_oVariableValue","_index","_vars"];
	_sVariableName = _this select 0;
	_oVariableValue = _this select 1;
	diag_log Format["(%1)[%2]: %3: %4",diag_tickTime,"netfunctions.sqf","XNetSetJIP", _this];
	If ((!isNil("JIPH")) And (!IsNil("_sVariableName")) And (_sVariableName != "")) then {
		_vars = JIPH getVariable "JIPH_ALL_MISSION_VARIABLES";
		if (isnil "_vars") then { _vars=[];};
		_index = _vars find _sVariableName;
		if (_index < 0) then {
			_index = count _vars;
			_vars set [_index, _sVariableName];
			JIPH setVariable ["JIPH_ALL_MISSION_VARIABLES", _vars, True];
		};
		JIPH setVariable [_sVariableName, _oVariableValue, True];
	};
};

XNetSyncJIP = {
	private ["_vars","_name","_val"];
	_vars = JIPH getVariable "JIPH_ALL_MISSION_VARIABLES";
	if (!isnil "_vars") then {
		{
			_name = _x;
			_val = JIPH getVariable _name;
			if (!isnil "_val") then {
				missionNameSpace setVariable [_name, _val];
				diag_log Format["JIP Sync: %1 = %2", _name, _val];
			};
		} foreach _vars;
	};
};

// There is an Hidden Helipad placed on the map in the mission itself which is global to all systems.
// The following call retrieves a "public" variable (synched between all systems) on the object.
// The second param is a default value in case it's nil :)
//
// Param 1: Variable Name
// Param 2: Default Value
// Return : The global var or the default.
XNetGetJIP = {
	// This calls the getVariable of the object.
	private["_sVariableName","_oVariableDefaultValue","_oVariableValue"];
	_sVariableName = _this select 0;
	_oVariableDefaultValue = _this select 1;
	diag_log Format["(%1)[%2]: %3: %4",diag_tickTime,"netfunctions.sqf","XNetGetJIP", _this];
	If (!isNil("JIPH")) then {
		_oVariableValue = JIPH getVariable _sVariableName;
		If (isNil("_oVariableValue")) then { _oVariableValue = _oVariableDefaultValue };
	} else {
		_oVariableValue = _oVariableDefaultValue;
	};
	_oVariableValue
};